package com.lkf.rpc.stub;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.internal.StringUtil;
import org.reflections.Reflections;

import java.lang.reflect.Method;
import java.util.Set;

/**
 * 网络容器响应结果控制器
 * 用于从netty的输入channel读取客户端请求的的数据
 */
public class InvokeHandler extends ChannelInboundHandlerAdapter {

    private String packagePath;

    public InvokeHandler(String packagePath) {
        this.packagePath = packagePath;
    }

    /**
     * 解析客户端发来的封装类，调用反射，得到客户端请求接口的实现类名称
     *
     * @param classInfo 客户端发来的封装类
     * @return 实现类名称
     */
    private String[] getImplClassName(ClassInfo classInfo) {
        int lastDot;
        String interfaceName;
        // 封装类不为空
        if (!StringUtil.isNullOrEmpty(classInfo.getClassName())) {
            // 封装类中要请求的接口包名不为空
            if (!StringUtil.isNullOrEmpty(classInfo.getPackageName())) {
                // 客户端封装类中要请求的接口包名与服务端等待处理请求的接口包名一致
                if (packagePath.equals(classInfo.getPackageName())) {
                    //定位接口字符串位置
                    lastDot = classInfo.getClassName().lastIndexOf(".");
                    // 拿到接口类名
                    interfaceName = classInfo.getClassName().substring(lastDot);
                    try {
                        // 生成接口类
                        Class superClass = Class.forName(packagePath + interfaceName);
                        // 反射出包路径下的类
                        Reflections reflections = new Reflections(packagePath);
                        // 拿到反射出的类的子类，也就是接口的实现类
                        Set<Class> implClassSet = reflections.getSubTypesOf(superClass);
                        // 实现类只能有1个，才能使客户端与服务端1对1远程调用
                        if (implClassSet.size() < 1) {
                            System.out.println("未在服务端找到" + packagePath + interfaceName + "的实现类！");
                            return null;
                        } else if (implClassSet.size() == 1) {
                            // 拿到实现类
                            Class[] classes = implClassSet.toArray(new Class[0]);
                            // 返回实现类名称
                            return new String[]{"0", classes[0].getName()};
                        } else {
//                            System.out.println("在服务端上" + packagePath + interfaceName + "有多个实现类！无法继续！");
                            return new String[]{"1", "在服务端上" + packagePath + interfaceName + "有多个实现类！无法继续！"};
                        }
                    } catch (ClassNotFoundException e) {
//                        System.out.println("未在服务端找到" + packagePath + interfaceName);
                        return new String[]{"2", "未在服务端找到" + packagePath + interfaceName};
                    }
                } else {
//                    System.out.println("从客户端中得到接口包名" + classInfo.getPackageName() + "与服务端" + packagePath + "不一致！");
                    return new String[]{"3", "未在服务端找到" + classInfo.getPackageName()};
                }
            } else {
//                System.out.println("没有从客户端的请求封装类中得到接口包名" + classInfo.getPackageName() + "！");
                return new String[]{"4", "没有从客户端的请求封装类中得到接口包名" + classInfo.getPackageName() + "！"};
            }
        } else {
//            System.out.println("没有从客户端的请求封装类中得到接口类名" + classInfo.getClassName() + "！");
            return new String[]{"5", "没有从客户端的请求封装类中得到接口类名" + classInfo.getClassName() + "！"};
        }
    }

    /**
     * 从netty的channel中读取客户端请求的数据，解析成ClassInfo封装类，并调用反射启动类中接口的实现类的具体方法
     *
     * @param ctx channelHandler上下文操作
     * @param msg 客户端请求内容，要转为ClassInfo对象
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //客户端请求内容，要转为ClassInfo对象
        ClassInfo classInfo = (ClassInfo) msg;
        // 根据ClassInfo中的类信息生成相应的类对象
        String[] result = getImplClassName(classInfo);
        if (result.length > 0) {
            if (result[0].equals("0")) {
                String className = result[1];
                Object clazz = Class.forName(className).newInstance();
                // 根据ClassInfo中的方法信息生成相应的方法对象
                Method method = clazz.getClass().getMethod(classInfo.getMethodName(), classInfo.getTypes());
                // 根据ClassInfo中的方法的参数类型
                Class<?>[] types = classInfo.getTypes();
                // 根据ClassInfo中的方法的参数值
                Object[] args = classInfo.getArgs();
                // 打印出客户端传来的封装类信息
                if (types.length == args.length && args.length > 0) {
                    System.out.println("接收到客户端请求接口方法名称为：" + clazz.getClass().getName() + "." + method.getName());
                    for (int i = 0; i < types.length; i++) {
                        System.out.println("第" + (i + 1) + "个参数为：" + types[i].getName() + "类型的" + args[i].toString());
                    }
                }
                //通过反射调用实现类的方法
                Object invokeResult = method.invoke(clazz, classInfo.getArgs());
                // 通过channel上下文写会方法调用结果
                ctx.writeAndFlush(invokeResult);
            } else {
                ctx.writeAndFlush(result[1]);
            }
        } else {
            ctx.writeAndFlush("服务端异常");
        }
    }
}
